package com.ry.controller;

import com.alibaba.fastjson.JSON;
import com.ry.common.utils.RedisUtil;
import com.ry.common.utils.RequestDistributionUtil;
import com.ry.model.ProductInventory;
import com.ry.request.InventoryRefreshCacheRequest;
import com.ry.request.InventoryUpdateDbRequest;
import com.ry.service.ProductInventoryService;
import com.ry.vo.Response;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * 库存controller
 */
@Slf4j
@RestController
@RequestMapping("/v1/productInventory")
public class ProductInventoryController {

    @Autowired
    private ProductInventoryService productInventoryService;

    @Autowired
    private RedisUtil redisUtil;

    @Autowired
    private RequestDistributionUtil requestDistributionUtil;

    /*

    http://localhost:8080//v1/productInventory/updateProductInventory?productId=3&inventoryCnt=100

    http://localhost:8080//v1/productInventory/getProductInventory?productId=1

     */

    /**
     * 读取商品库存缓存
     * @param productId
     */
    @RequestMapping("/getProductInventory")
    public ProductInventory getProductInventory(Long productId){
        log.info("======读取商品库存请求 入参->productId:{}",productId);

        InventoryRefreshCacheRequest inventoryRefreshCacheRequest =
                new InventoryRefreshCacheRequest(redisUtil, productInventoryService, productId);
        requestDistributionUtil.handleInventoryRequest(inventoryRefreshCacheRequest);

        ProductInventory productInventory = null;

        long startTime = System.currentTimeMillis();
        long waitTime = 0L;

        // 将刷新库存缓存请求扔给分发器去处理以后，就需要while(true)一会儿，在这里hang住
        // 去尝试等待前面有商品库存更新的操作，同时缓存刷新的操作，将最新的数据刷新到缓存中
        while (true){

            // 如果超过200ms也没有从缓存到获取到库存数据，就直接查询数据库
            if (waitTime > 200){
                break;
            }

            productInventory = redisUtil.getInventoryCache(productId);
            if (productInventory !=null ){
                log.info("======200毫秒内读取到了库存缓存 ->productId:{},inventory",productId,productInventory.getInventoryCnt());
                return productInventory;
            }else {
                try {
                    Thread.sleep(20);
                    waitTime = System.currentTimeMillis() - startTime;
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

        // 直接尝试从数据库中读取数据
        log.info("======200毫秒内没有读取到了库存缓存 ->productId:{}",productId);
        productInventory = productInventoryService.queryInventoryById(productId);
        if (productInventory == null){
            return new ProductInventory(productId, -1);
        }

        // 运行到这里只有三种情况：
        // 1、上一次也是读请求，数据写入了redis，但是redis LRU算法给清理掉了
        // 此时 flagMap [key,false] 会被过滤掉，不会执行刷新库存到缓存的方法，这时候需要强制刷新才行

        // 2、可能是在200ms内，就是读请求在队列中一直积压着，没有等待到它执行
        // 此时，应该要提示机器的配置了，你处理队列太慢了，这时候也添加一个强制刷新缓存的请求->[主要是处理第一种情况]

        // 3、数据库本身就没有，缓存穿透，请求到达数据库

        /**
            这时是不能这样做的，此时没有放在队列中去串行执行，还是有数据不一致的问题
            redisUtil.putInventoryCache(productInventory);
        */

        // 强制刷新
        InventoryRefreshCacheRequest forcedRefreshCacheRequest =
                new InventoryRefreshCacheRequest(redisUtil, productInventoryService, productId,true);
        requestDistributionUtil.handleInventoryRequest(forcedRefreshCacheRequest);

        return productInventory;
    }

    /**
     * 更新商品库存缓存
     * @param productInventory
     */
    @RequestMapping("/updateProductInventory")
    public Response updateProductInventory(ProductInventory productInventory){
        log.info("======更新商品库存请求 入参->productInventory:{}", JSON.toJSONString(productInventory));

        // TODO 更新缓存数据的时候出现并发怎么办？ 同时更新库存，更新的同时你再去查询库存？
        // TODO 所以需要将请求放到同一个队列中，串行执行

        InventoryUpdateDbRequest inventoryUpdateDbRequest =
                new InventoryUpdateDbRequest(redisUtil, productInventoryService, productInventory);

        requestDistributionUtil.handleInventoryRequest(inventoryUpdateDbRequest);

        return Response.success();
    }

}
